/**
 * 模块加载模拟实现: 
 * 1. 路径分析
 * 2. 缓存优化
 * 3. 文件定位
 * 4. 编译执行
 */

const fs = require('fs')
const path = require('path')
const vm = require('vm')

function Module (id) {
  this.id = id
  this.exports = {}
}

Module._resolveFilename = function (filename) {
  // 利用原生path模块把 filename 转为绝对路径
  let absPath = path.resolve(__dirname, filename)
  
  // 判断当前路径对应的内容是否存在
  if (fs.existsSync(absPath)){
    // 存在 则返回路径
    return absPath
  } else {
    // 不存在 进行文件定位
    // 2. 文件定位
    // 先进行.js .json后缀补足  注:真实nodejs代码会进行js json node补足
    let suffix = Object.keys(Module._extensions)
    for(var i=0;i<suffix.length;i++){
      let newPath = absPath + suffix[i]
      // 判断补足后的文件是否存在
      if(fs.existsSync(newPath)){
        return newPath
      }
    }
  }
  // 没找到文件则抛出错误 
  // 注: 真实node代码还会进行一系列其他路径的node_modules查找操作
  throw new Error(`${filename} is not exists`)
}

Module._extensions = {
  '.js'(module){
    // 读取
    let content = fs.readFileSync(module.id, 'utf-8')
    // 包装
    content = Module.wrapper[0] + content + Module.wrapper[1]
    // 改为vm执行函数
    let compileFn = vm.runInThisContext(content)
    // 准备参数值
    let exports = module.exports
    let dirname = path.dirname(module.id)
    let filename = module.id
    // 调用
    compileFn.call(exports, exports, myRequire, module, filename, dirname)
  },
  '.json'(module){
    // 读取、转换
    let content = JSON.parse(fs.readFileSync(module.id, 'utf-8'))

    // 导出
    module.exports = content
  }
}

Module.wrapper = [
  "(function(exports,require,module,__filename,__dirname){",
  "})"
]

Module._cache = {}

Module.prototype.load = function () {
  let extname = path.extname(this.id)

  Module._extensions[extname](this)
}

function myRequire (filename) {
  // 1. 获取绝对路径
  const mPath = Module._resolveFilename(filename)

  // 2. 缓存优先处理
  let cacheModule = Module._cache[mPath]
  // 有缓存，则优先使用缓存
  if (cacheModule) {
    return cacheModule.exports
  }

  // 3. 无缓存，先创建空对象加载目标模块
  let module = new Module(mPath)

  // 4. 缓存已加载过的模块
  Module._cache[mPath] = module

  // 5. 编译执行
  module.load()

  // 6. 返回数据
  return module.exports
}

let obj = myRequire('./v2')
console.log(obj)
